/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package server;

import chat.ChatCommand;
import chat.ChatMessage;
import chat.CmdEnum;
import com.ReliableDatagram;
import com.SequData;
import java.net.*;
import java.io.*;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
/**
 *
 * @author Mathias
 */
public class ServerListener extends Thread {

    private UDPChatServer server = null;
    private DatagramPacket myPacket = null;
    private List<InetSocketAddress> serverList = null;
    private Integer myPort = null;
    private Timer timer = new Timer();
    private int timeOut = 0;

    public ServerListener(UDPChatServer server, DatagramPacket myPacket, Integer myPort, List<InetSocketAddress> serverList, int timeOut) {
        this.myPacket = myPacket;
        this.myPort = myPort;
        this.serverList = serverList;
        this.timeOut = timeOut;
        this.server = server;
    }

    @Override
    public void run() {

        ReliableDatagram relData = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream in = null;
        ChatCommand cmdReply = null;
        InetSocketAddress sender = null;
        int inSequNr = 0;
        int outSequNr = 0;
        int expectedSequNr = 0;
        ClientData clientData = null;
        ClientTimer clientTimer = null;

        try {
            sender = (InetSocketAddress) myPacket.getSocketAddress();

            bis = new ByteArrayInputStream(myPacket.getData());
            in = new ObjectInputStream(bis);
            relData = (ReliableDatagram) in.readObject();
        } catch (Exception ex) {
            System.err.println("[ServerListener] exception: " + ex);
            return;
        }

        ChatCommand chatCmd = (ChatCommand) relData.getData();
        if (chatCmd == null) {
            System.err.println("[ServerListener] received packet that doesn't contain a ChatCommand: sequNr " + relData.getSequenceNumber() +
                    " data type " + relData.getData().getClass());
            return;
        }

        inSequNr = relData.getSequenceNumber();
        SequData sequData = server.getSequNrMap().get(sender);
        if (sequData != null) {
            expectedSequNr = sequData.getInSequNr() + 1;
            sequData.setInSequNr(inSequNr);
        }

        if (inSequNr < expectedSequNr && inSequNr > 0) {
            System.err.println("----> duplicate PACKET received SequNr: " + inSequNr + " expected: " + expectedSequNr + " <----");
            return;
        } else if (inSequNr > expectedSequNr) {
            System.err.println("----> missing PACKETs. Received SequNr: " + inSequNr + " expected: " + expectedSequNr + " <----");
        }

        //if (chatCmd.getCommand() != CmdEnum.POLL)
            System.out.println("[ServerListener] " + sender + " sequ " + inSequNr + " cmd " + chatCmd.getCommand() +
                " msg " + chatCmd.getMsgId());

        switch (chatCmd.getCommand()) {
            case SETNICKNAME:
                if (server.getClientMap().containsKey(chatCmd.getNickName())) {
                    cmdReply = new ChatCommand(CmdEnum.REFUSENICKNAME);
                } else if (serverList.size() == 0) {
                    cmdReply = new ChatCommand(CmdEnum.CONFIRMNICKNAME);
                } else if (serverList.contains(sender)) {
                    cmdReply = new ChatCommand(CmdEnum.CONFIRMNICKNAME);
                } else {
                    //hier in liste daten des nickname requests speichern (sender, nickname, ausstehende confirmations)
                    clientData = new ClientData(sender);
                    clientData.setRemainingNickConfirmations(serverList.size());
                    server.getClientMap().put(chatCmd.getNickName(), clientData);

                    for (InetSocketAddress serverEntry : serverList) {
                        SequData serverSequData = server.getSequNrMap().get(serverEntry);
                        serverSequData.setOutSequNr(serverSequData.getOutSequNr() + 1);
                        server.sendCommand(chatCmd, serverEntry, serverSequData.getOutSequNr());
                    }
                    return;
                }
                cmdReply.setNickName(chatCmd.getNickName());
                if (serverList.contains(sender)) {
                    sequData.setOutSequNr(sequData.getOutSequNr() + 1);
                    outSequNr = sequData.getOutSequNr();
                }
                server.sendCommand(cmdReply, sender, outSequNr);

                break;

            case CONFIRMNICKNAME:
                clientData = server.getClientMap().get(chatCmd.getNickName());
                clientData.setRemainingNickConfirmations(clientData.getRemainingNickConfirmations() - 1);
                if (clientData.getRemainingNickConfirmations() == 0) {
                    cmdReply = new ChatCommand(CmdEnum.CONFIRMNICKNAME);
                    cmdReply.setNickName(chatCmd.getNickName());
                    outSequNr = 1;
                    server.sendCommand(cmdReply, clientData.getSocketAddress(), outSequNr);
                    sequData = new SequData(0, outSequNr);
                    server.getSequNrMap().put(clientData.getSocketAddress(), sequData);
                    server.getClientMap().put(chatCmd.getNickName(), clientData);
                    clientTimer = new ClientTimer(chatCmd.getNickName(), server);
                    server.getTimerMap().put(chatCmd.getNickName(), clientTimer);
                    timer.schedule(clientTimer, timeOut);
                }
                break;

            case REFUSENICKNAME:
                clientData = server.getClientMap().remove(chatCmd.getNickName());
                cmdReply = new ChatCommand(CmdEnum.REFUSENICKNAME);
                cmdReply.setNickName(chatCmd.getNickName());
                server.sendCommand(cmdReply, clientData.getSocketAddress(), 0);
                break;

            case POLL:
                //calculate msgs to be deleted and the nick name with the lowest msg id
                int deleteMsgs = 0;
                if (chatCmd.getNickName() != null) {

                    //check if a timer is running for this client
                    if ((clientTimer = server.getTimerMap().remove(chatCmd.getNickName())) != null) {
                        //cancel the scheduled timer task and create a new one, register it and schedule it anew
                        clientTimer.cancel();
                        clientTimer = new ClientTimer(chatCmd.getNickName(), server);
                        server.getTimerMap().put(chatCmd.getNickName(), clientTimer);

                        clientData = server.getClientMap().get(chatCmd.getNickName());
                        clientData.setMsgId(server.getMsgCounter());
                        if (chatCmd.getNickName().equals(server.getNickWithLowestMsgId())) {
                            int lowestMsgId = clientData.getMsgId();
                            deleteMsgs = server.getMsgCounter();
                            Iterator<Map.Entry<String, ClientData>> iter = server.getClientMap().entrySet().iterator();
                            while (iter.hasNext()) {
                                Map.Entry<String, ClientData> entry = iter.next();
                                if (entry.getValue().getMsgId() < lowestMsgId) {
                                    lowestMsgId = entry.getValue().getMsgId();
                                    deleteMsgs = server.getMsgCounter() - lowestMsgId;
                                    server.setNickWithLowestMsgId(entry.getKey());
                                }
                            }
                        }
                    } else {
                        System.err.println("[ServerListener] " + chatCmd.getNickName() + " has been removed from clientMap");
                    }
                }

                //send back all msgs starting with msg id chatCmd.getMsgId()
                int startIndex = (chatCmd.getMsgId() < server.getMsgCounter() - server.getMessageQueue().size() ? server.getMsgCounter() - server.getMessageQueue().size() : chatCmd.getMsgId());
                for (int i = startIndex; i < server.getMsgCounter(); ++i) {
                    ChatMessage msg = server.getMessageQueue().get(i);
                    if (msg.getRecipients() != null && !msg.getRecipients().contains(chatCmd.getNickName())) continue;
                    cmdReply = new ChatCommand(CmdEnum.SENDMSG);
                    cmdReply.setChatMsg(msg);
                    cmdReply.setMsgId(i);

                    if (clientData != null) {
                        sequData.setOutSequNr(sequData.getOutSequNr() + 1);
                        outSequNr = sequData.getOutSequNr();
                    }
                    server.sendCommand(cmdReply, sender, outSequNr);

                }
                if (deleteMsgs == server.getMsgCounter() && deleteMsgs > 0) {
                    server.getMessageQueue().clear();
                } else if (deleteMsgs > 0) {
                    server.setMessageQueue(server.getMessageQueue().subList(deleteMsgs, server.getMsgCounter() - 1));
                }

                if (clientTimer != null) {
                    timer.schedule(clientTimer, timeOut);
                }

                break;

            case SENDMSG:
                //check if a timer is running for this client
                if ((clientTimer = server.getTimerMap().remove(chatCmd.getNickName())) != null) {
                    //cancel the scheduled timer task and create a new one, register it and schedule it anew
                    clientTimer.cancel();
                    clientTimer = new ClientTimer(chatCmd.getNickName(), server);
                    server.getTimerMap().put(chatCmd.getNickName(), clientTimer);
                    timer.schedule(clientTimer, timeOut);
                } else if (!serverList.contains(sender)) {
                    System.err.println("[ServerListener] " + chatCmd.getNickName() + " has been removed from clientMap");
                    break;
                }

                server.getMessageQueue().add(chatCmd.getChatMsg());
                server.setMsgCounter(server.getMsgCounter() + 1);

                if (!serverList.contains(sender)) {
                    for (InetSocketAddress serverEntry : serverList) {
                        SequData serverSequData = server.getSequNrMap().get(serverEntry);
                        serverSequData.setOutSequNr(serverSequData.getOutSequNr() + 1);
                        server.sendCommand(chatCmd, serverEntry, serverSequData.getOutSequNr());
                    }
                }
                break;

            default:
                System.err.println("[ServerListener] unexpected command " + chatCmd.getCommand());
                return;
        }
    }

}
